#include <memory>
#include <string>
#include <fuzzer/FuzzedDataProvider.h>
#include <sys/random.h>
#include <iostream>
#include <fstream>

#include <iprt/list.h>
#include <VBox/types.h>
#include <VBox/vmm/pdmthread.h>
#include <VBox/vmm/pdmsrv.h>
#include <VBox/vmm/pdmdev.h>
#include <VBoxVideo.h>
#include <DevVGA.h>
#include <DevVGA-SVGA3d.h>
#include <DevVGA-SVGA3d-internal.h>
#include <DevVGA-SVGA-internal.h>
#include <DevVGA-SVGA.h>
#include <PDMInternal.h>

#include "dev_vga_fuzzer.h"
#include "command_generator.h"
#include "mock_driver.h"
#include "mock_helper.h"

uint32_t kPorts[] = {
    SVGA_REG_ID ,
    SVGA_REG_ENABLE ,
    SVGA_REG_WIDTH ,
    SVGA_REG_HEIGHT ,
    SVGA_REG_MAX_WIDTH ,
    SVGA_REG_MAX_HEIGHT ,
    SVGA_REG_DEPTH ,
    SVGA_REG_BITS_PER_PIXEL ,       /* Current bpp in the guest */
    SVGA_REG_PSEUDOCOLOR ,
    SVGA_REG_RED_MASK ,
    SVGA_REG_GREEN_MASK ,
    SVGA_REG_BLUE_MASK ,
    SVGA_REG_BYTES_PER_LINE ,
    SVGA_REG_FB_START ,            /* (Deprecated) */
    SVGA_REG_FB_OFFSET ,
    SVGA_REG_VRAM_SIZE ,
    SVGA_REG_FB_SIZE ,

    /* ID 0 implementation only had the above registers, then the palette */
    SVGA_REG_ID_0_TOP ,

    SVGA_REG_CAPABILITIES ,
    SVGA_REG_MEM_START ,           /* (Deprecated) */
    SVGA_REG_MEM_SIZE ,
    SVGA_REG_CONFIG_DONE ,         /* Set when memory area configured */
    SVGA_REG_SYNC ,                /* See "FIFO Synchronization Registers" */
    SVGA_REG_BUSY ,                /* See "FIFO Synchronization Registers" */
    SVGA_REG_GUEST_ID ,            /* (Deprecated) */
    SVGA_REG_DEAD ,                /* Drivers should never write this. */
    SVGA_REG_CURSOR_X ,            /* (Deprecated) */
    SVGA_REG_CURSOR_Y ,            /* (Deprecated) */
    SVGA_REG_CURSOR_ON ,           /* (Deprecated) */
    SVGA_REG_HOST_BITS_PER_PIXEL , /* (Deprecated) */
    SVGA_REG_SCRATCH_SIZE ,        /* Number of scratch registers */
    SVGA_REG_MEM_REGS ,            /* Number of FIFO registers */
    SVGA_REG_NUM_DISPLAYS ,        /* (Deprecated) */
    SVGA_REG_PITCHLOCK ,           /* Fixed pitch for all modes */
    SVGA_REG_IRQMASK ,             /* Interrupt mask */

    /* Legacy multi-monitor support */
    SVGA_REG_NUM_GUEST_DISPLAYS ,/* Number of guest displays in X/Y direction */
    SVGA_REG_DISPLAY_ID ,        /* Display ID for the following display attributes */
    SVGA_REG_DISPLAY_IS_PRIMARY ,/* Whether this is a primary display */
    SVGA_REG_DISPLAY_POSITION_X ,/* The display position x */
    SVGA_REG_DISPLAY_POSITION_Y ,/* The display position y */
    SVGA_REG_DISPLAY_WIDTH ,     /* The display's width */
    SVGA_REG_DISPLAY_HEIGHT ,    /* The display's height */

    /* See "Guest memory regions" below. */
    SVGA_REG_GMR_ID ,
    SVGA_REG_GMR_DESCRIPTOR ,
    SVGA_REG_GMR_MAX_IDS ,
    SVGA_REG_GMR_MAX_DESCRIPTOR_LENGTH ,

    SVGA_REG_TRACES ,            /* Enable trace-based updates even when FIFO is on */
    SVGA_REG_GMRS_MAX_PAGES ,    /* Maximum number of 4KB pages for all GMRs */
    SVGA_REG_MEMORY_SIZE ,       /* Total dedicated device memory excluding FIFO */
    SVGA_REG_COMMAND_LOW ,       /* Lower 32 bits and submits commands */
    SVGA_REG_COMMAND_HIGH ,      /* Upper 32 bits of command buffer PA */

    /*
     * Max primary memory.
     * See SVGA_CAP_NO_BB_RESTRICTION.
     */
    SVGA_REG_MAX_PRIMARY_MEM ,
    SVGA_REG_MAX_PRIMARY_BOUNDING_BOX_MEM ,

    /*
     * Legacy version of SVGA_REG_GBOBJECT_MEM_SIZE_KB for drivers that
     * don't know how to convert to a 64-bit byte value without overflowing.
     * (See SVGA_REG_GBOBJECT_MEM_SIZE_KB).
     */
    SVGA_REG_SUGGESTED_GBOBJECT_MEM_SIZE_KB ,

    SVGA_REG_DEV_CAP ,           /* Write dev cap index, read value */
    SVGA_REG_CMD_PREPEND_LOW ,
    SVGA_REG_CMD_PREPEND_HIGH ,
    SVGA_REG_SCREENTARGET_MAX_WIDTH ,
    SVGA_REG_SCREENTARGET_MAX_HEIGHT ,
    SVGA_REG_MOB_MAX_SIZE ,
    SVGA_REG_BLANK_SCREEN_TARGETS ,
    SVGA_REG_CAP2 ,
    SVGA_REG_DEVEL_CAP ,

    SVGA_REG_GUEST_DRIVER_ID ,
    SVGA_REG_GUEST_DRIVER_VERSION1 ,
    SVGA_REG_GUEST_DRIVER_VERSION2 ,
    SVGA_REG_GUEST_DRIVER_VERSION3 ,
    SVGA_REG_CURSOR_MOBID ,
    SVGA_REG_CURSOR_MAX_BYTE_SIZE ,
    SVGA_REG_CURSOR_MAX_DIMENSION ,

    SVGA_REG_FIFO_CAPS ,
    SVGA_REG_FENCE ,

    SVGA_REG_RESERVED1 ,
    SVGA_REG_RESERVED2 ,
    SVGA_REG_RESERVED3 ,
    SVGA_REG_RESERVED4 ,
    SVGA_REG_RESERVED5 ,
    SVGA_REG_SCREENDMA ,


    SVGA_REG_GBOBJECT_MEM_SIZE_KB ,


    SVGA_REG_REGS_START_HIGH32 ,
    SVGA_REG_REGS_START_LOW32 ,
    SVGA_REG_FB_START_HIGH32 ,
    SVGA_REG_FB_START_LOW32 ,


    SVGA_REG_MSHINT ,

    SVGA_REG_IRQ_STATUS ,
    SVGA_REG_DIRTY_TRACKING ,

    SVGA_REG_TOP ,

};

void FuzzVGACmd(PPDMDEVINS pDevIns, PPDMTHREAD pThread);
void FuzzVGAPio(PPDMDEVINS pDevIns, uint32_t port, uint32_t value);
struct VGAState;


void DevVGAFuzzer::Init() {
    memset(&noop_helper_, 0, sizeof(PDMDEVHLPR3));
    noop_helper_.pfnSUPSemEventSignal = &pfnSupSemEventSignalNoop;
    noop_helper_.pfnPCIPhysWrite = &pfnPCIPhysWriteNoop;

    noop_helper_.pfnCritSectEnter = &pfnCritSectEnterNoop;
    noop_helper_.pfnCritSectLeave = &pfnCritSectLeaveNoop;
    noop_helper_.pfnCritSectLeave = &pfnCritSectLeaveNoop;
    noop_helper_.pfnPCISetIrq = &pfnPCISetIrqNoop;
    noop_helper_.pfnThreadDestroy = &pfnThreadDestroyNoop;
    noop_helper_.pfnMmio2ControlDirtyPageTracking = &pfnMmio2ControlDirtyPageTrackingNoop;
    noop_helper_.pfnSUPSemEventClose = &pfnSUPSemEventCloseNoop;

    // Read from the guest is the only one that is not actually noop but that
    // feeds in random data.
    noop_helper_.pfnPCIPhysRead = &pfnPCIPhysReadNoop;
    noop_helper_.pfnPhysRead = &pfnPhysReadNoop;

    noop_helper_.pfnPhysWrite = &pfnPhysWriteNoop;

    thread_.enmState = PDMTHREADSTATE_RUNNING;
    vga_state_.svga.fEnabled = true;
    vga_state_.svga.f3DEnabled = true;
    vga_state_.svga.fConfigured = true;
    vga_state_.svga.cbFIFO = 2048;

    memset(&device_state_, 0, sizeof(VGADeviceState));
    svga_state_ = (VMSVGAR3STATE*)malloc(sizeof(VMSVGAR3STATE));
    memset(svga_state_, 0, sizeof(VMSVGAR3STATE));
    ptr_vga_state_cc_ = (PVGASTATER3) (void*)&device_state_.service_instance.achInstanceData[0];
    ptr_vga_state_cc_->svga.pSvgaR3State = svga_state_;
    ptr_vga_state_cc_->svga.pau32FIFO = (uint32_t*)malloc(vga_state_.svga.cbFIFO);
    ptr_vga_state_cc_->svga.pFIFOIOThread = &thread_;

    ptr_vga_state_cc_->svga.pbVgaFrameBufferR3 = (uint8_t*)malloc(VMSVGA_VGA_FB_BACKUP_SIZE);
    ptr_vga_state_cc_->pbVRam = (uint8_t*)malloc(VMSVGA_VGA_FB_BACKUP_SIZE);


    pFuncsGBO_ = (VMSVGA3DBACKENDFUNCSGBO *)malloc(sizeof(VMSVGA3DBACKENDFUNCSGBO));
    pFuncsGBO_->pfnScreenTargetBind = &pfnScreenTargetBindNoop;
    pFuncsGBO_->pfnScreenTargetUpdate = &pfnScreenTargetUpdateNoop;
    svga_state_->pFuncsGBO = pFuncsGBO_;


    struct PDMIDISPLAYCONNECTOR *mock_driver =  CreateDriver();
    ptr_vga_state_cc_->pDrv = mock_driver;


    p3dState_ = (PVMSVGA3DSTATE)malloc(sizeof(VMSVGA3DSTATE));
    memset(p3dState_, 0, sizeof(VMSVGA3DSTATE));
    ptr_vga_state_cc_->svga.p3dState = p3dState_; 


    device_state_.service_instance.pvInstanceDataR3 = &vga_state_;
    device_state_.service_instance.pHlpR3 = &noop_helper_;

    // Setting up the command buffers.
    svga_state_->apCmdBufCtxs[0] = nullptr;
    svga_state_->apCmdBufCtxs[1] = nullptr;
    InitCmdBufCtx();

    memset(&svga_state_->CmdBufCtxDC, 0 , sizeof(VMSVGACMDBUFCTX));
    svga_state_->CmdBufCtxDC.listSubmitted.pNext = &svga_state_->CmdBufCtxDC.listSubmitted;
    svga_state_->CmdBufCtxDC.listSubmitted.pPrev = &svga_state_->CmdBufCtxDC.listSubmitted;
    svga_state_->CmdBufCtxDC.cSubmitted = 0;

    vmsvgaR3Reset(&device_state_.service_instance);

}

void DevVGAFuzzer::ResetCmdBufCtx() {
    if (svga_state_->apCmdBufCtxs[0] != nullptr)  {
        auto ptr = svga_state_->apCmdBufCtxs[0]->listSubmitted.pNext;
        for(int i = 0; i < svga_state_->apCmdBufCtxs[0]->cSubmitted; i++) {
            auto next = ptr->pNext;
            free(ptr);
            ptr = next;
        }
        free(svga_state_->apCmdBufCtxs[0]);
        svga_state_->apCmdBufCtxs[0] = nullptr;
    }
    if (svga_state_->apCmdBufCtxs[1] != nullptr)  {
        auto ptr = svga_state_->apCmdBufCtxs[1]->listSubmitted.pNext;
        for(int i = 0; i < svga_state_->apCmdBufCtxs[1]->cSubmitted; i++) {
            auto next = ptr->pNext;
            free(((VMSVGACMDBUF*)ptr)->pvCommands);
            free(ptr);
            ptr = next;
        }
        free(svga_state_->apCmdBufCtxs[1]);
        svga_state_->apCmdBufCtxs[1] = nullptr;
    }
    InitCmdBufCtx();
}

void DevVGAFuzzer::InitCmdBufCtx() {
    if (svga_state_->apCmdBufCtxs[0] == nullptr) {
        svga_state_->apCmdBufCtxs[0] = (VMSVGACMDBUFCTX*) malloc(sizeof(VMSVGACMDBUFCTX));
        memset(svga_state_->apCmdBufCtxs[0], 0 , sizeof(VMSVGACMDBUFCTX));
        svga_state_->apCmdBufCtxs[0]->listSubmitted.pNext = &svga_state_->apCmdBufCtxs[0]->listSubmitted;
        svga_state_->apCmdBufCtxs[0]->listSubmitted.pPrev = &svga_state_->apCmdBufCtxs[0]->listSubmitted;
        svga_state_->apCmdBufCtxs[0]->cSubmitted = 0;
    }

    if (svga_state_->apCmdBufCtxs[1] == nullptr) {
        svga_state_->apCmdBufCtxs[1] = (VMSVGACMDBUFCTX*) malloc(sizeof(VMSVGACMDBUFCTX));
        memset(svga_state_->apCmdBufCtxs[1], 0 , sizeof(VMSVGACMDBUFCTX));
        svga_state_->apCmdBufCtxs[1]->listSubmitted.pNext = &svga_state_->apCmdBufCtxs[1]->listSubmitted;
        svga_state_->apCmdBufCtxs[1]->listSubmitted.pPrev = &svga_state_->apCmdBufCtxs[1]->listSubmitted;
        svga_state_->apCmdBufCtxs[1]->cSubmitted = 0;
    }
}


FuzzedDataProvider* DevVGAFuzzer::GetFdp() {
    return fdp_.get();
}

void DevVGAFuzzer::Fuzz(const uint8_t *data, size_t size) {
    if (size == 0) {
        return;
    }

    fdp_ = std::make_unique<FuzzedDataProvider>(data, size);

    uint8_t operation_count = fdp_->ConsumeIntegralInRange<uint8_t>(1, 30);

    while (operation_count > 0) {
        operation_count--;
        auto op =fdp_->ConsumeEnum<Operation>();
        InitCmdBufCtx();

        switch (op) {
            case Operation::kPIO: {
                                      uint32_t port = fdp_->ConsumeIntegral<uint32_t>() % (sizeof(kPorts)/sizeof(uint32_t));
                                      port = kPorts[port];
                                      uint32_t value = fdp_->ConsumeIntegral<uint32_t>();

                                      /*
                                      // Write the reproducer file:
                                      // [4 byte operation kPIO] [ 4 byte port ] [4 byte value]
                                      std::ofstream file(*output_filename, std::ios::binary | std::ios::app);
                                      if (!file.is_open()) {
                                          printf("could not open file, aborting fuzzer\n");
                                          exit(-1);
                                      }

                                      file.write(reinterpret_cast<const char*>(&op), sizeof(int32_t));
                                      file.write(reinterpret_cast<const char*>(&port), sizeof(int32_t));
                                      file.write(reinterpret_cast<const char*>(&value), sizeof(int32_t));

                                      file.close();
                                      */

                                      FuzzVGAPio(&device_state_.service_instance, port, value);

                                      break;
                                  }
            case Operation::kCommand: {
                                          ResetCmdBufCtx();
                                          if( svga_state_->apCmdBufCtxs[0] == NULL) return;
                                          cmd_buf_ = (VMSVGACMDBUF*)malloc(sizeof(VMSVGACMDBUF));
                                          memset(cmd_buf_, 0, sizeof(VMSVGACMDBUF));

                                          RTListAppend(&svga_state_->apCmdBufCtxs[0]->listSubmitted, &cmd_buf_->nodeBuffer);
                                          svga_state_->apCmdBufCtxs[0]->cSubmitted++;

                                          cmd_buf_->nodeBuffer.pPrev = &svga_state_->apCmdBufCtxs[0]->listSubmitted;
                                          cmd_buf_->nodeBuffer.pNext = &svga_state_->apCmdBufCtxs[0]->listSubmitted;

                                          uint32_t total_length = 0;
                                          uint8_t *cmd_buf = AllocateRandomCommands(fdp_.get(), total_length);
                                          if (cmd_buf == nullptr) {
                                              free(cmd_buf_);
                                              return;
                                          }

                                          /*

                                          std::ofstream file(*output_filename, std::ios::binary | std::ios::app);
                                          if (!file.is_open()) {
                                              printf("could not open file, aborting fuzzer\n");
                                              exit(-1);
                                          }

                                          file.write(reinterpret_cast<const char*>(&op), sizeof(int32_t));
                                          file.write(reinterpret_cast<const char*>(&total_length), sizeof(int32_t));
                                          file.write(reinterpret_cast<const char*>(cmd_buf), total_length);

                                          file.close();
                                          */

                                          cmd_buf_->hdr.length = total_length;
                                          cmd_buf_->pvCommands = cmd_buf;
                                          FuzzVGACmd(&device_state_.service_instance, &thread_);
                                          break;
                                      }
            default:
                                      break;
        }
    }
}

DevVGAFuzzer *fuzzer = nullptr;
std::string *output_filename = nullptr;

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    //int main() {
    if (fuzzer == nullptr)
        fuzzer = new DevVGAFuzzer();
    /*
    if (output_filename == nullptr) {
        output_filename = new std::string();
        uint32_t rand;
        getrandom(&rand, sizeof(uint32_t), 0);
        char filename[32];
        memset(filename, 0, 32);
        sprintf(filename, "fuzzer-%u.input", rand);
        *output_filename = filename;
        printf("output file: %s\n", filename);
    }
    */
    fuzzer->Fuzz(data,size);
    delete fuzzer;
    fuzzer = nullptr;
    return 0;
}
